using System;
using System.Collections;
using System.Text;
using System.Security.Cryptography;
 
namespace Framework
{
    public static class AlgorithmUtil
    {

        #region ToBase64
        /// <summary>
        /// Static method that converts a byte array to a string that has been base-64 encoded.
        /// </summary>
        /// <param name="data">Byte array of data to encode.</param>
        /// <returns>Base-64 encoded string.</returns>
        public static string ToBase64(byte[] data)
        {
            if (data == null)
            {
                throw new ArgumentNullException("data", "data can not be null.");
            }

            return Convert.ToBase64String(data).Trim();
        }

        /// <summary>
        /// Static method that converts a byte array to a string that has been base-64 encoded.
        /// </summary>
        /// <param name="data">String of data to encode.</param>
        /// <returns>Base-64 encoded string.</returns>
        public static string ToBase64(string data)
        {
            if (data == null)
            {
                throw new ArgumentNullException("data", "data can not be null.");
            }

            return ToBase64(Encoding.UTF8.GetBytes(data));
        }
        #endregion

        #region FromBase64
        /// <summary>
        /// Static method that takes a string encoded with base-64, and returns a byte array.
        /// </summary>
        /// <param name="base64Text">Base-64 encoded string</param>
        /// <returns>Byte array of un-encoded data bytes.</returns>
        public static byte[] FromBase64(string base64Text)
        {
            if (base64Text == null)
            {
                throw new ArgumentNullException("base64Text", "base64Text can not be null.");
            }

            return Convert.FromBase64String(base64Text);
        }
        #endregion

        #region FromBase64AsString
        /// <summary>
        /// Static method that takes a string encoded with base-64, and returns a byte array.
        /// </summary>
        /// <param name="base64Text">Base-64 encoded string</param>
        /// <returns>String of un-encoded data bytes.</returns>
        public static string FromBase64AsString(string base64Text)
        {
            if (base64Text == null)
            {
                throw new ArgumentNullException("base64Text", "base64Text can not be null.");
            }

            return Encoding.UTF8.GetString(Convert.FromBase64String(base64Text));
        }
        #endregion

        #region EncryptString
        /// <summary>
        /// Encrypts a string and returns in Base64 format.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="passphrase">The passphrase.</param>
        /// <remarks>
        /// http://www.dijksterhuis.org/encrypting-decrypting-string/
        /// </remarks>
        /// <returns>Base64 Encrypted String</returns>
        public static string EncryptString(string message, string passphrase)
        {
            byte[] results;
            var utf8 = new UTF8Encoding();

            // Step 1. We hash the passphrase using MD5
            // We use the MD5 hash generator as the result is a 128 bit byte array
            // which is a valid length for the TripleDES encoder we use below

            var hashProvider = new MD5CryptoServiceProvider();
            var trippleDesKey = hashProvider.ComputeHash(utf8.GetBytes(passphrase));

            // Step 2. Create a new TripleDESCryptoServiceProvider object & setup the encoder
            var trippleDesAlgorithm = new TripleDESCryptoServiceProvider
                                          {
                                              Key = trippleDesKey,
                                              Mode = CipherMode.ECB,
                                              Padding = PaddingMode.PKCS7
                                          };

            // Step 3. Convert the input string to a byte[]
            var dataToEncrypt = utf8.GetBytes(message);

            // Step 4. Attempt to encrypt the string
            try
            {
                var encryptor = trippleDesAlgorithm.CreateEncryptor();
                results = encryptor.TransformFinalBlock(dataToEncrypt, 0, dataToEncrypt.Length);
            }
            finally
            {
                // Clear the TripleDes and Hashprovider services of any sensitive information
                trippleDesAlgorithm.Clear();
                hashProvider.Clear();
            }

            // Step 5. Return the encrypted string as a base64 encoded string
            return Convert.ToBase64String(results);
        }
        #endregion

        #region DecryptString
        /// <summary>
        /// Decrypts the string.
        /// </summary>
        /// <param name="message">The message as Base64 encryped string.</param>
        /// <param name="passphrase">The passphrase.</param>
        /// http://www.dijksterhuis.org/encrypting-decrypting-string/
        /// <returns>Message</returns>
        public static string DecryptString(string message, string passphrase)
        {
            try
            {
                byte[] results;
                var utf8 = new UTF8Encoding();

                // Step 1. We hash the passphrase using MD5
                // We use the MD5 hash generator as the result is a 128 bit byte array
                // which is a valid length for the TripleDES encoder we use below

                var hashProvider = new MD5CryptoServiceProvider();
                var trippleDesKey = hashProvider.ComputeHash(utf8.GetBytes(passphrase));

                // Step 2. Create a new TripleDESCryptoServiceProvider object and setup the decoder
                var trippleDesAlgorithm = new TripleDESCryptoServiceProvider
                                              {
                                                  Key = trippleDesKey,
                                                  Mode = CipherMode.ECB,
                                                  Padding = PaddingMode.PKCS7
                                              };

                // Step 3. Convert the input string to a byte[]
                var dataToDecrypt = Convert.FromBase64String(message);

                // Step 4. Attempt to decrypt the string
                try
                {
                    var decryptor = trippleDesAlgorithm.CreateDecryptor();
                    results = decryptor.TransformFinalBlock(dataToDecrypt, 0, dataToDecrypt.Length);
                }
                finally
                {
                    // Clear the TripleDes and Hashprovider services of any sensitive information
                    trippleDesAlgorithm.Clear();
                    hashProvider.Clear();
                }

                // Step 5. Return the decrypted string in UTF8 format
                return utf8.GetString(results);
            }
            catch (Exception ex)
            {
                LogUtil.Error(ex);

                return "Failed to Decrypt";
            }
        }
        #endregion

        #region HashMd5 / HashMd5AsHex
        public static string HashMd5(string data)
        {
            return GetHash(new MD5CryptoServiceProvider(), Encoding.Unicode, StrUtil.DefaultValue(data));
        }

        public static string HashMd5AsHex(string data)
        {
            return GetHashAsHex(new MD5CryptoServiceProvider(), Encoding.Unicode, StrUtil.DefaultValue(data));
        }
        #endregion

        #region HashSha1CSP
        public static string HashSha1CSP(string v)
        {
            return GetHash(new SHA1CryptoServiceProvider(), Encoding.UTF8, StrUtil.DefaultValue(v));
        }
        #endregion

        #region HashSha1Managed
        public static string HashSha1Managed(string v)
        {
            return GetHash(new SHA1Managed(), Encoding.UTF8, StrUtil.DefaultValue(v));
        }
        #endregion

        #region HashSha384Managed
        public static string HashSha384Managed(string v)
        {
            return GetHash(new SHA384Managed(), Encoding.UTF8, StrUtil.DefaultValue(v));
        }
        #endregion

        #region HashSha512Managed
        public static string HashSha512Managed(string v)
        {
            return GetHash(new SHA512Managed(), Encoding.UTF8, StrUtil.DefaultValue(v));
        }
        #endregion

        #region GetHash
        private static string GetHash(HashAlgorithm hashAlg, Encoding encoding, string input)
        {
            return Convert.ToBase64String(hashAlg.ComputeHash(encoding.GetBytes(input)));
        }
        #endregion

        #region GetHashAsHex
        /// <summary>
        /// Gets the hash as hex value.
        /// </summary>
        /// <remarks>
        /// http://blog.stevex.net/index.php/c-code-snippet-creating-an-md5-hash-string/
        /// </remarks>
        /// <param name="hashAlg">The hash alg.</param>
        /// <param name="encoding">The encoding.</param>
        /// <param name="input">The input.</param>
        /// <returns></returns>
        private static string GetHashAsHex(HashAlgorithm hashAlg, Encoding encoding, string input)
        {
            var hash = hashAlg.ComputeHash(encoding.GetBytes(input));

            var result = new StringBuilder();

            for (int i = 0; i < hash.Length; i++)
			{
			    result.Append(hash[i].ToString("X2"));
			}
            return result.ToString();
        } 
        #endregion

        #region CalculateLuhn
        public static int CalculateLuhn(string number)
        {
            if (number == null || number.Trim().Length < 1)
            {  // Must be 1 or more digits
                throw new ArgumentException("Number to calculate LUHN check digit is too small");
            }

            int total = 0;
            bool isDouble = true;

            for (int i = number.Length - 1; i >= 0; i--)
            {
                if (Char.IsDigit(number[i]))
                {
                    int digit = Convert.ToInt32(number.Substring(i, 1));
                    int digitValue = (isDouble ? digit * 2 : digit);
                    total += (digitValue >= 10 ? (1 + (digitValue - 10)) : digitValue);
                }
                else
                {
                    throw new ArgumentException("Invalid character found during LUHN Check, LUHN calculation will only work on numbers");
                }
                isDouble = !isDouble;
            }

            // Go to the next number divisible by 10, the Check digit is that number minus total
            total = ((total + 10) - (total % 10)) - total;

            // 10 is 0, 1-9 stay as they are
            return (total == 10 ? 0 : total);
        }
        #endregion

        #region CheckLuhn
        /// <summary>
        /// Validates a number to see if it is a valid Luhn number (see luhn algorithm)
        /// </summary>
        /// <param name="number">Number to validate</param>
        /// <returns>True if the supplied number is a valid Luhn number (see luhn algorithm)</returns>
        public static bool CheckLuhn(string number)
        {
            if (number == null || number.Trim().Length < 2)
            {  // Must be 1 or more digits
                throw new ArgumentException("Invalid number to check LUHN digit");
            }

            string numberLeft = number.Substring(0, number.Length - 1);
            int numberRight = NumUtil.ParseInt(number.Substring(number.Length - 1));

            return CalculateLuhn(numberLeft) == numberRight;
        }
        #endregion

    }
}
